package com.guli.mall.pms.controller;

import com.guli.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.*;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/*
 * @Author 罗
 * @date 2020/10/12 - 11:28 上午
 */
@RestController
@Slf4j
public class RedissonTestController {

    @Resource
    RedissonClient redissonClient;

    @Resource
    StringRedisTemplate stringRedisTemplate;

    /**
     * 测试 Redisson 的分布式锁
     */
    @GetMapping("/hello")
    public R hello() {
        RLock lock = redissonClient.getLock("hello-lock");

        /**
         * 1、如果我们未指定锁的超时时间，就使用 看门狗默认时间 30s commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout();
         * 只要占锁成功就会启动一个定时任务【重新给锁设置过期时间，锁的过期时间就是看门狗的默认时间】
         * */
        lock.lock(); // 阻塞式等待，默认锁的续期时间是 30s。
        /**
         * 2、当自定义锁的时间之后，看门狗将不会自动续期锁的时间，所以，自动解锁的时间一定要大于业务的执行时间
         * 如果我们传递了锁的超时时间，就发送给 redis
         * */
//         lock.lock(10,TimeUnit.SECONDS);

        try {
            // 任务执行时间过长，锁自动续期（redis的key = hello-lock 的 ttl 会自动刷新为30s）
            // 运行期间自动给锁加30s，不用担心业务时间过长，锁因为没有续期而被删掉
            // 加上锁的业务只要运行完成，就不会给当前锁自动续期，即使不手动删除，锁也会自动的在30s后自动删除
            log.info("{}正在处理请求，线程id = {}", Thread.currentThread().getName(), Thread.currentThread().getId());
            try {
                TimeUnit.SECONDS.sleep(30);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("{}执行完毕，线程id = {}", Thread.currentThread().getName(), Thread.currentThread().getId());

        } finally {
            lock.unlock();
        }
        return R.ok().put("data", Thread.currentThread().getName() + "\t线程id = " + Thread.currentThread().getId() + "\t提供的服务：Hello");
    }

    /**
     * 测试读写锁
     * 这里是写锁
     * 1、读写锁保证能读取到最新的数据，修改期间，写锁是一个排他锁（互斥锁，独享锁）。
     * 读锁是一个共享锁
     * 写锁没有释放，读锁就必须等待
     * 2、如果是读先占用到锁，那么写锁也必须等待读锁释放，才能进行写操作
     * <p>
     * 读+读：相当于无锁，并发读，只会在redis中记录好，所有当前的读锁。他们都会同时加锁成功
     * 写+读：写锁没有释放，读锁就必须等待
     * 写+写：阻塞方式
     * 读+写：读锁没有释放，写锁必须等待
     * <p>
     * 总结：只要有写锁的存在，读写都必须等待
     *
     * @param content
     * @return
     */
    @GetMapping("/write/{content}")
    public R write(@PathVariable("content") String content) {

        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("test-read-write-lock");

        RLock rLock = readWriteLock.writeLock();

        rLock.lock();
        try {

            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stringRedisTemplate.opsForValue().set("temp-content", content);

        } finally {
            rLock.unlock();
        }

        return R.ok();
    }

    /**
     * 测试读写锁
     * 这里是写锁
     *
     * @return
     */
    @GetMapping("/read")
    public R read() {

        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("test-read-write-lock");

        RLock rLock = readWriteLock.readLock();

        rLock.lock();

        String s = null;
        try {

            s = stringRedisTemplate.opsForValue().get("temp-content");

        } finally {
            rLock.unlock();
        }

        return R.ok().put("data", s);
    }

    /**
     * 模拟闭锁
     */
    @GetMapping("/lockDoor")
    public String lockDoor() throws InterruptedException {
        RCountDownLatch countDownLatch = redissonClient.getCountDownLatch("lock-door");

        countDownLatch.trySetCount(5);

        countDownLatch.await();

        return "锁门了，走人了，放假了";
    }

    @GetMapping("/gogogo/{clazz}")
    public String gogogo(@PathVariable("clazz") String clazz) {

        RCountDownLatch countDownLatch = redissonClient.getCountDownLatch("lock-door");

        countDownLatch.countDown();

        return clazz + "班走人了";
    }

    @GetMapping("/parkingCar")
    public String parkingCar() throws InterruptedException {
        RSemaphore semaphore = redissonClient.getSemaphore("parking-car-lock");

        semaphore.acquire();

        return "正在停车";
    }

    @GetMapping("/tryParkingCar")
    public String tryParkingCar()  {
        RSemaphore semaphore = redissonClient.getSemaphore("parking-car-lock");

        boolean b = semaphore.tryAcquire();

        if(b){
            return "正在停车";

        }else{
            return "当前车位已满，请去其他地方";
        }

    }

    @GetMapping("/go")
    public String go() {
        RSemaphore semaphore = redissonClient.getSemaphore("parking-car-lock");

        semaphore.release();

        return "开走了";
    }

    @GetMapping("/setNumberOfParkingSpaces/{number}")
    public String setNumberOfParkingSpaces(@PathVariable("number") Integer number) {

        RSemaphore semaphore = redissonClient.getSemaphore("parking-car-lock");

        if (semaphore.trySetPermits(number)) {
            return "车位已经被设置为：" + number;

        }
        return "车位已经设置好了，不能再设置了";
    }

}
